(レポート) DVO304: AWS CloudFormation ベストプラクティス #reinvent
よく訓練されたアップル信者、都元です。re:Invent 2015セッションレポートです。公開してしばらく経っているいるのにこのセッションが残っているってのは、私のために取っておいてくれたんでしょうかw
CloudFormationとは
過去エントリーCloudFormation入門をご覧ください。4行で表現するとこんなかんじです。
- AWSインフラ構成のテンプレートを作ると
- CloudFormationがAWSリソースをプロビジョニングする
- テンプレートはinfrastructure-as-codeとして「バージョン管理」「複製」「更新」が可能
- 開発工程、CI / CD、各種管理ツールと連携する
CloudFormation Designer
さて、今回発表となったCloudFormationの新機能に「CloudFormation Designer」というものがあります。従来より、CloudFormationテンプレートはJSONで記述するものでした。
実用的なインフラ構成のテンプレートは1000行を越えることもザラで、その編集については正直苦労する点が多かったのも事実です。外部ツール等を利用してJSON記述の辛みを軽減する試みは多々ありました。が、結局はGUIエディタが欲しかったんですよね。ただ、GUIのエディタ作るのってかなり大変なんです。個人的な話ですが筆者はその昔ER図のエディタを作っていまして、だいぶ苦労しました。欲しいのも分かるんですが、作るのが大変なのもよく分かるので、みんな口には出さなかったのかな、なんて思っています。
そんな中。公式なGUIエディタとして発表されたのがCloudFormation Designerです。そう、こういうのこそ公式でサポートされていると嬉しいものですね。
既存のテンプレートを図として可視化し、ドラッグ&ドロップで編集できます。編集結果はリアルタイムでJSON側に反映されます。
CloudFormation Designerで開くファイルは「ローカルファイル」「S3上のファイル」「既存のスタック」と、必要な物は全て網羅されています。バリデーションもリアルタイムで行われるため、テンプレート編集の負荷はだいぶ軽減されると思います。
各リソースは、1つのアイコンとして表現されるもの(S3バケットやEC2インスタンス等)、コンテナとして表現されるもの(VPC、サブネット、Beanstalkアプリケーション)、コネクションとして表現されるもの(VPCGatewayAttachmentやSubnetRouteTableAssociation等)があります。それぞれのリソースの特性としてDesignerの中に見た目が登録されているんですね。
ビジュアルエディタとなると、ドラッグ&ドロップによるGUI編集にばかり目が向きがちですが、CloudFormation DesignerはJSONを直接編集する場合でも威力を発揮します。Ctrl+Spaceキーでは入力補完が使えます。そしてCtrl+\ ではインデントのフォーマットができます。できるらしいです。Macでは確認できませんでしたがorz
従来のJSONテンプレートが持っていなかったが、GUIとすることで保持する必要が出てきてしまった情報として「座標やサイズ」の情報があります。これはテンプレートの中にMetadataという形で埋め込まれ、再編集する際にはこの情報に基づいてレイアウトを行う仕組みなっています。
CloudFormationで新たにサポートされたサービス・リソース
その他、このre:Inventに合わせて、CloudFormationが新たにサポートしたリソースがいくつかあります。
- AWS Lambda (event source)
- Auto Scaling (spot fleet)
- Amazon RDS for Aurora
- AWS Directory Service (Simple AD)
- AWS CodeDeploy
- Amazon WorkSpaces
こちらはそのうち当ブログでそれぞれ紹介されていくと思います。
CloudFormationの言語機能
CloudFormationでは、JSON上で利用できる「組み込み関数(Intrinsic Functuon)」がいくつかあります。この辺は復習ですね。
ドキュメントを参照してみてください。
CloudFormationに対する拡張
スタックイベント
CloudFormationは、スタック及び各種配下リソースの作成、更新や削除のタイミングで、SNSにメッセージを飛ばすことができます。このメッセージを例えばLambda等で処理することによって行える拡張の可能性はかなり広いものとなります。
Lambdaベースのカスタムリソース
CloudFormationの最もマニアックな機能であろうと思われる「カスタムリソース」を利用すると、さらに複雑なことが可能です。
CloudFormationでは、S3バケットやEC2インスタンス等、AWSのリソースしか制御できないと無意識に考えがちです。が、カスタムリソースという仕組みを使えば、どんなリソースでもプロビジョニング、そして状態の管理が可能です。もちろん、そのロジックは自分で書かなければいけませんが…。
そのリソースの制御に関するロジックを、現在はLambdaで記述できるようになっています。
これにより、例えば「最新のAMI ID」や「他のCloudFormationスタック内のリソースの物理ID」を参照することができるようになったりします。
CloudFormationのセキュリティ
スタックポリシー
CloudFormationスタックには「スタックポリシー」というプロパティがあります。
このポリシードキュメントによって、ユーザに対して「特定のテンプレートを使ったCreateStackやUpdateStackのみを許可する」といった設定ができるようになります。
また、ユーザに対して「EC2インスタンス型(AWS::EC2::Instance)のリソース(及びその他指定したいくつかの型のリソース)だけ、作成を許す」や「IAMのリソース(AWS::IAM::*)の作成は許さない」といった指定も可能です。
CreateStack及びUpdateStackアクションに対するResourceTypesパラメータ
こちらはしれっと紹介された新機能です、多分。
CreateStack及びUpdateStack時に、このパラメータに対して「今からこの型のリソースをcreate / updateしようとしている」という意思表示を行うことができます。このパラメータを指定した時、ここに含まれない型のリソースを作成・更新しようとした場合はエラーとなります。
これにより、意図しないリソースが不意に更新されてしまう危険をかなりの確率で回避できます。
ちなみに、このパラメータを指定しなかった場合は、CloudFormationは全ての型のリソースが指定されているものとして動きますので、従来までの動きは壊さないようになっています。
テンプレートのベストプラクティス
AWSのリージョン間の再利用性
環境依存・リージョン依存のリソースに注意しましょう。
例えば、AMIのIDはリージョン内固有の値であり、別のリージョンに行けば別のIDになってしまいます。従って、AMI IDはテンプレート内で直接指定しないようにしましょう。
パラメータにしておけばかなり柔軟ですが、毎回指定するのは大変かもしれません。その場合は、後述のMappingを使いましょう。
また、リージョンによっては利用できるインスタンスタイプ(EC2やRDS等)が異なる可能性があります。また、AWS APIのエンドポイントホスト名やARNも異なります。
これらを意識しながらテンプレートを記述すると、どのリージョンでも使えるテンプレートになります。
擬似パラメータ
CloudFormationには、CreateStack時に指定する「パラメータ」という仕組みがありますが、それに似た仕組みとして「擬似パラメータ」というものがあります。
パラメータは明示的に指定するものですが、擬似パラメータは、そのスタックの置かれた環境に応じて自動的に設定されるパラメータです。具体的には、AWSアカウントIDや、プロビジョニング先のリージョン等を下記のような記述で参照できます。
{"Ref":"AWS::AccountId"}
→ 123456789012 等{"Ref":"AWS::Region"}
→ ap-northeast-1 等{"Ref":"AWS::StackName"}
→ teststack等、任意{"Ref":"AWS::StackId"}
→ arn:aws:cloudformation:ap-northeast-1:123456789012:stack/teststack/51af3dc0-da77-11e4-872e-1234567db123 等
この仕組みを使うと、さらにテンプレートの再利用性を上げられますね。
マッピング
Mappingに記述しておいて、リージョン毎に適切なAMI IDが選ばれるようにしていおくのが現実的です。
例えば、ELBのログを配置するS3バケットに対しては、ELBがPutObjectできるようにポリシーを指定する必要があります。
ELBのログは、クロスアカウントアクセスで配信されてきます。リージョン毎に、どのAWSアカウントIDから配信されてくるか、ドキュメントには明示されています。
具体的に、us-east-1では127311923021から、東京では582318560864から配信されてきます。これを上手に設定するにはMappingが有効です。
"LogBucketPolicy" : { "Type" : "AWS::S3::BucketPolicy", "Properties" : { "Bucket" : { "Ref" : "LogBucket" }, "PolicyDocument" : { "Id" : "LogBucketPolicy", "Statement" : [{ "Sid" : "WriteAccess", "Action" : [ "s3:PutObject" ], "Effect" : "Allow", "Resource" : { "Fn::Join" : [ "", [ "arn:aws:s3:::", { "Ref" : "LogBucket" } , "/*" ]]}, "Principal" : { "AWS" : { "Fn::FindInMap" : [ "ELBLogger", { "Ref": "AWS::Region" }, "AccountID" ]} } }] } } },
まず、このようにポリシーを記述します。ポイントは { "Fn::FindInMap" : [ "ELBLogger", { "Ref": "AWS::Region" }, "AccountID" ]}
の部分で、ここがリージョンに応じて、上記ELBのAWSアカウントIDに置き換わります。
このFindInMapという記述を有効にするために、ELBLogger
という名前のMappingを下記のように定義しておきます。
"Mappings": { "ELBLogger": { "us-east-1": { "AccountID": "127311923021" }, "us-west-2": { "AccountID": "797873946194" }, "us-west-1": { "AccountID": "027434742980" }, "eu-west-1": { "AccountID": "156460612806" }, "ap-southeast-1": { "AccountID": "114774131450" }, "ap-southeast-2": { "AccountID": "783225319266" }, "ap-northeast-1": { "AccountID": "582318560864" }, "sa-east-1": { "AccountID": "507241528517" }, "us-gov-west-1": { "AccountID": "048591011584" } }, ... }
コンディション
その他、EC2-VPC環境だけではなく EC2-Classic環境にも対応したテンプレートを作る場合等は「コンディション」が有効です。
"Conditions": { "Is-EC2-VPC": { "Fn::Or": [ { "Fn::Equals": [ { "Ref": "AWS::Region" }, "eu-central-1" ] }, { "Fn::Equals": [ { "Ref": "AWS::Region" }, "cn-north-1" ] } ]}, "Is-EC2-Classic": { "Fn::Not": [{ "Condition": "Is-EC2-VPC" }]} }
スライドでは上記の通り、eu-central-1及びcn-north-1リージョンではEC2-VPC環境、それ以外はEC2-Classic環境として判定するものが例示されていました。が、これは特定のAWSアカウントでの状況を反映したものでありましょう。
リージョン間での再利用性は保たれていると思いますが、AWSアカウント間での再利用性は無いと思いますのでご注意下さい。